#include <retro.h>
#include "retro.library.h"
#include "retro.luainterface.h"

using namespace Retro;
using namespace Retro::Data;
using namespace Retro::Scripting;
using namespace SPLib::Geom;

INT LuaInterface::LuaCallback(lua_State *L)
{
  LuaInterface* wScript = (LuaInterface*)lua_topointer(L, lua_upvalueindex(1));
  ScriptCallback wFunc = (ScriptCallback)lua_topointer(L, lua_upvalueindex(2));

  wScript->mPushCount = 0;

  wFunc(wScript);
  wScript->clearStringBuffer();

  return wScript->mPushCount;
}

INT LuaInterface::LuaHandler(lua_State *L)
{
  LuaInterface* wScript = (LuaInterface*)lua_topointer(L, lua_upvalueindex(1));

  wScript->mErrorMsg = StringToUnicode(lua_tostring(wScript->mLuaState, 1));

  return 0;
}

LuaInterface::LuaInterface() :
  ScriptInterface(ScriptLua)
{
  mLuaState   = luaL_newstate();
  mPushCount  = 0;
  mErrorMsg   = NULL;

  luaL_openlibs(mLuaState);

  lua_pushlightuserdata(mLuaState, (PVOID)this);
  lua_pushcclosure(mLuaState, LuaHandler, 1);
  mLuaHandler = luaL_ref(mLuaState, LUA_REGISTRYINDEX);
}

LuaInterface::~LuaInterface()
{
  lua_close(mLuaState);
}

VOID  LuaInterface::add(ScriptValue* iValue)
{
  push(iValue->value, iValue->type);
  lua_setglobal(mLuaState, iValue->name);
}

VOID  LuaInterface::add(ScriptValue* iValues, UINT iCount, PCSTR iName)
{
  push(iValues, iCount);
  lua_setglobal(mLuaState, iName);
}

VOID  LuaInterface::push(PVOID iValue, ValueType iType)
{
  switch (iType)
  {
  case TypeInteger:
    lua_pushinteger(mLuaState, *(INT*)iValue);
    break;
  case TypeLong:
    lua_pushinteger(mLuaState, (INT)*(LONG*)iValue);
    break;
  case TypeUnsigned:
    lua_pushunsigned(mLuaState, *(UINT*)iValue);
    break;
  case TypeFloat:
    lua_pushnumber(mLuaState, *(FLOAT*)iValue);
    break;
  case TypeDouble:
    lua_pushnumber(mLuaState, *(DOUBLE*)iValue);
    break;
  case TypeBoolean:
    lua_pushboolean(mLuaState, *(BOOL*)iValue != 0);
    break;
  case TypeString:
    lua_pushstring(mLuaState, (PCSTR)iValue);
    break;
  case TypeFunction:
    lua_pushlightuserdata(mLuaState, (PVOID)this);
    lua_pushlightuserdata(mLuaState, (PVOID)iValue);
    lua_pushcclosure(mLuaState, LuaCallback, 2);
    break;
  case TypeScript:
    luaL_loadstring(mLuaState, (PCSTR)iValue);
    break;
  case TypeNull:
    lua_pushnil(mLuaState);
    break;
  default:
    return;
  }

  ++mPushCount;
}

VOID  LuaInterface::push(ScriptValue* iValues, UINT iCount)
{
  UINT wPrevCount = mPushCount;

  lua_createtable(mLuaState, 0, iCount);

  for (UINT i = 0; i < iCount; ++i)
  {
    lua_pushstring(mLuaState, iValues[i].name);

    if (iValues[i].type == TypeObject)
      push((ScriptValue*)iValues[i].value, iValues[i].param);
    else
      push(iValues[i].value, iValues[i].type);

    lua_settable(mLuaState, -3);
  }

  mPushCount = wPrevCount + 1;
}

VOID  LuaInterface::get(UINT iIndex, PVOID oValue, ValueType iType)
{
  ASSERT(iIndex >= 0 && (INT)iIndex <= lua_gettop(mLuaState));
  ASSERT(!lua_isnil(mLuaState, iIndex));

  switch (iType)
  {
  case TypeInteger:
    *(INT*)oValue = (INT)lua_tointeger(mLuaState, iIndex);
    break;
  case TypeLong:
    *(LONG*)oValue = (LONG)lua_tointeger(mLuaState, iIndex);
    break;
  case TypeUnsigned:
    *(UINT*)oValue = (UINT)lua_tounsigned(mLuaState, iIndex);
    break;
  case TypeFloat:
    *(FLOAT*)oValue = (FLOAT)lua_tonumber(mLuaState, iIndex);
    break;
  case TypeDouble:
    *(DOUBLE*)oValue = (DOUBLE)lua_tonumber(mLuaState, iIndex);
    break;
  case TypeBoolean:
    *(BOOL*)oValue = (BOOL)lua_toboolean(mLuaState, iIndex);
    break;
  case TypeString:
    *(PCSTR*)oValue = lua_tostring(mLuaState, iIndex);
    break;
  case TypeFunction:
    // TODO: retrieve lua function
    break;
  }
}

ValueType LuaInterface::getType(UINT iIndex)
{
  ASSERT(iIndex >= 0 && (INT)iIndex <= lua_gettop(mLuaState));

  switch (lua_type(mLuaState, iIndex))
  {
  case LUA_TNIL:
    return TypeNull;
  case LUA_TBOOLEAN:
    return TypeBoolean;
  case LUA_TNUMBER:
    return TypeNumber;
  case LUA_TSTRING:
    return TypeString;
  case LUA_TFUNCTION:
    return TypeFunction;
  }

  return TypeUnknown;
}

INT   LuaInterface::getParamCount()
{
  return lua_gettop(mLuaState);
}

PCTSTR LuaInterface::getErrorMessage()
{
  return mErrorMsg;
}

VOID  LuaInterface::doCall()
{
  switch (lua_pcallk(mLuaState, 0, 0, -2, 0, NULL))
  {
  case LUA_OK:
    clearStringBuffer();
    break;
  case LUA_ERRRUN:
    setError(ErrorRuntime);
    break;
  case LUA_ERRMEM:
    setError(ErrorMemory);
    break;
  case LUA_ERRERR:
  default:
    setError(ErrorUnknown);
    break;
  }
}

BOOL  LuaInterface::call(PCSTR iName)
{
  setError(ErrorNone);
  clearStringBuffer();

  delete[] mErrorMsg;
  mErrorMsg = NULL;

  lua_rawgeti(mLuaState, LUA_REGISTRYINDEX, mLuaHandler);
  lua_getglobal(mLuaState, iName);
  doCall();

  return getError() == ErrorNone;
}

BOOL  LuaInterface::run(PCSTR iScript)
{
  setError(ErrorNone);
  clearStringBuffer();

  delete[] mErrorMsg;
  mErrorMsg = NULL;

  lua_rawgeti(mLuaState, LUA_REGISTRYINDEX, mLuaHandler);

  INT wResult = luaL_loadstring(mLuaState, iScript);

  if (wResult == LUA_OK)
  {
    doCall();
  }
  else
  {
    mErrorMsg = StringToUnicode(lua_tostring(mLuaState, 2));

    switch (wResult)
    {
    case LUA_ERRSYNTAX:
      setError(ErrorSyntax);
      break;
    case LUA_ERRMEM:
      setError(ErrorMemory);
      break;
    case LUA_ERRERR:
    default:
      setError(ErrorUnknown);
      break;
    }
  }

  return getError() == ErrorNone;
}

BOOL  LuaInterface::validate(PCSTR iScript)
{
  INT iResult = luaL_loadstring(mLuaState, iScript);

  if (iResult == LUA_OK)
    lua_pop(mLuaState, 1);

  return iResult == LUA_OK;
}
